////////////////////////////////////////////////
// DESCRIPTION:
//    Devive Manager PnP Notification implementation
//
// Legal Notices:
//     Copyright (c) 2008, Telliam Consulting, LLC.
//     All rights reserved.
//
//     Redistribution and use in source and binary forms, with or without modification,
//     are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//
//     * Neither the name of Telliam Consulting nor the names of its contributors
//       may be used to endorse or promote products derived from this software
//       without specific prior written permission.
//
// Disclaimer:
//     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//     IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
//     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
//     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
//     OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
//     OF THE POSSIBILITY OF SUCH DAMAGE.  
//
#include <windows.h>
#include <FusionWare\Win32.h>

using namespace FusionWare::Threads;
using namespace FusionWare::Win32;

// {secret} typedef functor type for the stub thread to simplify the code a bit
typedef Loki::Functor<DWORD, TYPELIST_1(CDeviceNotifications::DeviceNotificationFunction)>StubThreadFunction;

bool CDeviceNotifications::StartNotifications(DeviceNotificationFunction NotifFunc, const GUID& Guid, int Priority, bool PriExt, bool GetAll)
{
    // don't allow multiple starts!
    if(IsRunning())
        return false;

    // create stop event as Manual reset with no name.
    if(!this->StopEvent.Create(NULL,TRUE))
        return false;

    this->Guid = Guid;
    this->GetAll = GetAll;
    
    // Start the thread...
    // This creates a new Thread Functor with it's parameter bound to the NotifFunc
    // paramter to allow use with the normal thread object support.
    //
    if(!Start(BindFirst(StubThreadFunction(this, &CDeviceNotifications::NotificationThread), NotifFunc), Priority, PriExt))
    {
        this->StopEvent.Close();
        return false;
    }

    return true;
}

bool CDeviceNotifications::StopNotifications(DWORD TimeOut)
{
    this->StopEvent.Set();
    return WaitForExit(TimeOut) ? true : false;
}

// Implementation Notes:
//    Just redirects to the RequestNotificationsfunction on the
//    thread for this function.
//
DWORD CDeviceNotifications::NotificationThread(DeviceNotificationFunction NotifFunc)
{
    RequestNotifications(NotifFunc, this->Guid, INFINITE, this->GetAll, this->StopEvent.GetHandle());
    return 0;
}

bool CDeviceNotifications::RequestNotifications(DeviceNotificationFunction pFunc, const GUID& Guid, DWORD TimeOut, bool GetAll, HANDLE hStopEvent)
{
    DWORD flags;
    DWORD numRead;
    CMsgQueue msgQueue;
    HANDLE hNotif;

    // Device notifications are a variable length structure
    // A DEVDETAIL structure folowwed by a string
    BYTE buf[sizeof(DEVDETAIL) + MAX_DEVCLASS_NAMELEN * sizeof(TCHAR)];

    // create the queue for this thread to use
    if(!msgQueue.CreateReadOnly(sizeof(buf)))
    {
        ERRORMSG(1,(_T("Failed to create queue: (0x%X)\r\n"),GetLastError()));
        return false;
    }
                                                                          
    // kick off the notifications (if Get all is true several msgs will be added to the Q)
    hNotif = RequestDeviceNotifications(&Guid, msgQueue.GetHandle(), GetAll ? TRUE:FALSE);
    if(NULL==hNotif)
    {
        ERRORMSG(1,(_T("::RequestDeviceNotifications failed - (0x%X)\r\n"),GetLastError()));
        return false;
    }

    // wait on either the Queue OR the Stop Event if available
    HANDLE waitHandles[] = { msgQueue.GetHandle(), hStopEvent };
    while(WAIT_OBJECT_0 == WaitForMultipleObjects(hStopEvent == NULL ? 1:2, waitHandles, FALSE, TimeOut) )
    {
        if(!msgQueue.Read(buf,sizeof(buf),&numRead, &flags))
        {
            ERRORMSG(1,(_T("Failed to read from queue: (0x%X)\r\n"),GetLastError()));
            return false;
        }

        pFunc(*((const DEVDETAIL*)buf), flags);
    }

    StopDeviceNotifications(hNotif);
    return true;
}
